Skip to content

perf(android): Parse app start profiling config without JsonSerializer - #5867

Open
runningcode wants to merge 4 commits into
mainfrom
no/java-621-app-start-profiling-config-deserialization
Open

perf(android): Parse app start profiling config without JsonSerializer#5867
runningcode wants to merge 4 commits into
mainfrom
no/java-621-app-start-profiling-config-deserialization

Conversation

@runningcode

@runningcode runningcode commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

📜 Description

JsonSerializer is a massive object with 60 serializers but we only need one of them in SentryPerformanceProvider. We can't re-use the one held in SentryOptions because the SentryPerformanceProvider content provider has a higher priority than the SentryInitProvider.
So instead we create a serializer with only the serializer we need for this specific purpose.

Measured on a Pixel 10 (API 37) with androidx Microbenchmark, release build:

allocations time (median)
via JsonSerializer (before) 221 7548 ns
direct Deserializer (after) 33 3687 ns

allocationCount is deterministic, so the allocation figures are exact. The timings are steady-state (JIT-warmed, clocks unlocked), so they understate the real cold-start saving — warmup amortizes away the class loading and verification of all those deserializer classes, which the real one-shot path pays in full. No claim is made about measurable end-to-end startup improvement; ~3.9 µs is small against a ~1 s cold start. The point is main-thread work removed from before Application.onCreate.

💡 Motivation and Context

Perf improvement

Related: #5708 and #5709 were previously closed for the same "small allocation win, not worth the trade off" reason.

GH Issue: #5707
Linear: #5707

💚 How did you test it?

Unit tests

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

createAndStartContinuousProfiler also calls SentryOptions.empty(), for TracesSampler. That one genuinely needs a mutable options object (setProfileSessionSampleRate) and only runs when a sampled continuous-profiling config exists, so it is deliberately left alone.

@linear-code

linear-code Bot commented Jul 29, 2026

Copy link
Copy Markdown

JAVA-621

@sentry

sentry Bot commented Jul 29, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.51.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 416.94 ms 486.71 ms 69.77 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
6b019b7 343.31 ms 417.23 ms 73.91 ms
d15471f 342.08 ms 415.44 ms 73.35 ms
8687935 332.52 ms 362.23 ms 29.71 ms
5b1a06b 352.27 ms 413.70 ms 61.43 ms
91bb874 314.47 ms 440.00 ms 125.53 ms
0ee65e9 321.06 ms 361.24 ms 40.18 ms
e63ad34 323.67 ms 390.33 ms 66.67 ms
33a08cc 267.08 ms 340.45 ms 73.37 ms
27d7cf8 397.90 ms 498.65 ms 100.75 ms
ee747ae 405.43 ms 485.70 ms 80.28 ms

App size

Revision Plain With Sentry Diff
6b019b7 0 B 0 B 0 B
d15471f 1.58 MiB 2.13 MiB 559.54 KiB
8687935 1.58 MiB 2.19 MiB 619.17 KiB
5b1a06b 0 B 0 B 0 B
91bb874 1.58 MiB 2.13 MiB 559.07 KiB
0ee65e9 0 B 0 B 0 B
e63ad34 0 B 0 B 0 B
33a08cc 1.58 MiB 2.12 MiB 555.28 KiB
27d7cf8 1.58 MiB 2.12 MiB 549.42 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB

Previous results on branch: no/java-621-app-start-profiling-config-deserialization

Startup times

Revision Plain With Sentry Diff
7354f33 322.00 ms 364.18 ms 42.18 ms

App size

Revision Plain With Sentry Diff
7354f33 0 B 0 B 0 B

runningcode and others added 3 commits July 30, 2026 11:21
…r (JAVA-621)

SentryPerformanceProvider.launchAppStartProfiler ran in ContentProvider.onCreate
— main thread, before Application.onCreate, on every cold start — and built a
full JsonSerializer(SentryOptions.empty()) to read one small config file.

That path only ever reads options.getLogger(), but JsonSerializer's constructor
registers every known deserializer to use exactly one of them. Calling the
deserializer directly cuts the parse from 221 to 33 allocations and ~7.5us to
~3.7us (Pixel 10, androidx Microbenchmark, allocationCount is deterministic).

Malformed input still yields null so callers keep reporting it as a
deserialization failure rather than a read error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…onfig

Narrow the deserialization catch from Throwable to Exception. The vendored JSON
reader signals bad input with IOException (MalformedJsonException, EOFException),
IllegalStateException on token type mismatch, and NumberFormatException on an
unparseable number — all Exception subclasses. Catching Throwable additionally
swallowed Error, which is never a recoverable "config file is bad" signal.

This also restores parity with JsonSerializer.deserialize, which catches
Exception, so the replaced behaviour is matched exactly rather than widened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@runningcode
runningcode force-pushed the no/java-621-app-start-profiling-config-deserialization branch from 162d4db to 8e27d0c Compare July 30, 2026 09:22
@runningcode
runningcode marked this pull request as ready for review July 30, 2026 09:27

@0xadam-brown 0xadam-brown left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ftw!

* them.
*
* <p>Returns null on malformed input, matching what {@code JsonSerializer.deserialize} did, so
* callers keep reporting it as a deserialization failure rather than a read error. The vendored

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit if you want it: Personally I'd either inline "The vendored..." above the catch clause, or drop it altogether. (The latter makes sense to me as we're all aware of the virtues of not catching Errors.)

@romtsn romtsn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

Drop the paragraph enumerating which exception types the vendored JSON reader
throws. That catch (Exception) does not swallow Error is a language-level
given, and listing the reader's internal exception types invites the comment
to drift as that code changes. The rationale a reader cannot infer from the
code — why the deserializer is called directly, and why null is returned
instead of rethrowing — stays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants